package org.codefilarete.stalactite.sql.ddl.structure;

import org.codefilarete.stalactite.query.api.JoinLink;
import org.codefilarete.stalactite.query.api.Selectable;
import org.codefilarete.stalactite.sql.ddl.DDLAppender;
import org.codefilarete.stalactite.sql.ddl.Size;

import javax.annotation.Nullable;

/**
 * Column of a table.
 * 
 * @param <O> the Java type this columns is mapped to
 * @author Guillaume Mary
 */
public class Column<T extends Table, O> implements Selectable<O>, JoinLink<T, O> {
	
	private final T table;
	private final String name;
	private final Class<O> javaType;
	@Nullable
	private Size size;
	@Nullable
	private Boolean nullable;
	private final String absoluteName;
	private final String alias;
	private boolean primaryKey;
	private boolean autoGenerated;
	
	/**
	 * Build a column
	 */
	public Column(T owner, String name, Class<O> javaType) {
		this(owner, name, javaType, null);
	}
	
	/**
	 * Build a column with a size
	 */
	public Column(T owner, String name, Class<O> javaType, @Nullable Size size) {
		this.table = owner;
		this.name = name;
		this.javaType = javaType;
		this.size = size;
		this.absoluteName = getTable().getName() + "." + getName();
		this.alias = getTable().getName() + "_" + getName();
		this.nullable = null;
	}
	
	public Column(T owner, String name, Class<O> javaType, @Nullable Size size, @Nullable Boolean nullable) {
		this.table = owner;
		this.name = name;
		this.javaType = javaType;
		this.size = size;
		this.absoluteName = getTable().getName() + "." + getName();
		this.alias = getTable().getName() + "_" + getName();
		this.nullable = nullable;
	}
	
	@Override
	public T getOwner() {
		return getTable();
	}
	
	@Override
	public String getExpression() {
		return this.name;
	}
	
	@Override
	public Class<O> getJavaType() {
		return this.javaType;
	}
	
	public T getTable() {
		return table;
	}
	
	public String getName() {
		return name;
	}
	
	/**
	 * Gives the column name prefixed by table name. Allows column identification in a schema.
	 *
	 * @return getTable().getName() + "." + getName()
	 */
	public String getAbsoluteName() {
		return absoluteName;
	}
	
	/**
	 * Provides a default alias usable for select clause
	 * @return getTable().getName() +"_" + getName()
	 */
	public String getAlias() {
		return alias;
	}
	
	@Nullable
	public Size getSize() {
		return size;
	}
	
	public void setSize(@Nullable Size size) {
		this.size = size;
	}
	
	/**
	 *
	 * @return null if the property wasn't set, certainly meaning that the column is left nullable
	 * @see org.codefilarete.stalactite.sql.ddl.DDLTableGenerator#generateCreateColumn(Column, DDLAppender)
	 */
	@Nullable
	public Boolean isNullable() {
		return nullable;
	}
	
	public void setNullable(Boolean nullable) {
		this.nullable = nullable;
	}
	
	/**
	 * Fluent API
	 * @param nullable is this Column is optional or mandatory
	 * @return this
	 */
	public Column<T, O> nullable(boolean nullable) {
		setNullable(nullable);
		return this;
	}
	
	/**
	 * Fluent API. Set this column as not nullable.
	 * @return this
	 */
	public Column<T, O> notNull() {
		return nullable(false);
	}
	
	public boolean isPrimaryKey() {
		return primaryKey;
	}
	
	public void setPrimaryKey(boolean primaryKey) {
		this.primaryKey = primaryKey;
	}
	
	/**
	 * Set this column as primary of the table, or participate to it if there are several ones
	 *
	 * @return this
	 */
	public Column<T, O> primaryKey() {
		setPrimaryKey(true);
		return this;
	}
	
	public boolean isAutoGenerated() {
		return autoGenerated;
	}
	
	public void setAutoGenerated(boolean autoGenerated) {
		if (!isPrimaryKey() && autoGenerated) {
			throw new UnsupportedOperationException("Auto generate operation is only supported for primary key, please declare the column as primary key before");
		}
		this.autoGenerated = autoGenerated;
	}
	
	public Column<T, O> autoGenerated() {
		setAutoGenerated(true);
		return this;
	}
	
	/* Do not implement equals/hashCode, which can be tempting (based on absolute name for instance)
	 * because it breaks possibility to add clones of column (through clones of table) in select clause when
	 * joining several times same table is necessary : for that case column clones must be considered distinct
	 */
	
	/**
	 * Overridden only for simple print (debug)
	 */
	@Override
	public String toString() {
		return getAbsoluteName();
	}
}
